#include "md5_anim_parser.h"

using namespace krryn::data;

md5_anim_parser::md5_anim_parser(const std::string &a_Filename, size_t a_MemoryBudgetBytes)
	: md5_parser(a_Filename, a_MemoryBudgetBytes), 
	  m_JointData(0), m_BaseFrameData(0), m_FrameData(0), 
	  m_NumAnimatedComponents(-1) {}

md5_anim_parser::~md5_anim_parser(){
	delete[] m_JointData;
	delete[] m_BaseFrameData;
	delete[] m_FrameData;
}

void md5_anim_parser::parse(){
	try{
		header();
		settings();
		hierarchy_block();
		bounds_block();
		baseframe_block();
		frame_block();
	}catch(md5_lexer_eof &){
		if(!is_valid_eof()){
			error("unexpected end of file");
		}
	}catch(const md5_token_exception &a_Token){
		unexpected(a_Token.m_ExpectedTag, a_Token.m_CurrentTag);
	}catch(const md5_parse_error &a_ParseError){
		throw a_ParseError;
	}catch(...){
		error("fatal error");
	}
}

void md5_anim_parser::settings(){
	int l_Count;

	switch(m_Look.m_Tag){
		case num_frames:
			match(num_frames);

			l_Count = m_Look.as_int();

			if(m_Animation.m_NumFrames != -1){
				unexpected(num_frames);
			}

			request_allocation(sizeof(md5_joint*) * l_Count);
			request_allocation(sizeof(math::aabb) * l_Count);

			m_Animation.m_NumFrames = l_Count;
			m_Animation.m_SkeletalFrames = new md5_joint*[l_Count];
			m_Animation.m_BoundingBoxes = new math::aabb[l_Count];

			match(integer);
			settings();
			break;
		case num_joints:
			match(num_joints);

			l_Count = m_Look.as_int();

			if(m_Animation.m_NumJoints != -1 || m_Animation.m_NumFrames == -1){
				unexpected(num_joints);
			}

			request_allocation(sizeof(md5_joint) * l_Count * m_Animation.m_NumFrames);

			// also request allocation for temporary data
			request_allocation(sizeof(joint_data) * l_Count);
			request_allocation(sizeof(base_frame_data) * l_Count);

			m_Animation.m_NumJoints = l_Count;

			for(int i = 0; i < m_Animation.m_NumFrames; i++){
				m_Animation.m_SkeletalFrames[i] = new md5_joint[l_Count];
			}

			m_JointData = new joint_data[l_Count];
			m_BaseFrameData = new base_frame_data[l_Count];

			match(integer);
			settings();
			break;
		case frame_rate:
			match(frame_rate);

			if(m_Animation.m_FrameRate != -1){
				unexpected(frame_rate);
			}

			l_Count = m_Look.as_int();

			if(l_Count < 0) error("Can't have negative framerate");

			m_Animation.m_FrameRate = l_Count;

			match(integer);
			settings();
			break;
		case num_animated_components:
			match(num_animated_components);

			l_Count = m_Look.as_int();

			if(m_NumAnimatedComponents != -1){
				unexpected(num_animated_components);
			}

			request_allocation(sizeof(float) * l_Count);

			m_NumAnimatedComponents = l_Count;
			m_FrameData = new float[l_Count];

			match(integer);
			settings();
			break;
	}
}

void md5_anim_parser::hierarchy_block(){
	match(hierarchy);
	match(open_brace);

	for(int i = 0; i < m_Animation.m_NumJoints; i++){
		joints(i);
	}

	match(close_brace);
}

void md5_anim_parser::joints(int a_Index){
	joint_data &l_Joint = m_JointData[a_Index];

	l_Joint.m_Name = m_Look.as_string();
	match(string);

	l_Joint.m_Parent = m_Look.as_int();
	match(integer);

	l_Joint.m_Flags = m_Look.as_int();
	match(integer);

	l_Joint.m_StartIndex = m_Look.as_int();
	match(integer);
}

void md5_anim_parser::bounds_block(){
	match(bounds); match(open_brace);

	for(int i = 0; i < m_Animation.m_NumFrames; i++){
		bounds_data(i);
	}

	match(close_brace);
}

void md5_anim_parser::bounds_data(int a_Index){
	m_Animation.m_BoundingBoxes[a_Index].m_Min = vector();
	m_Animation.m_BoundingBoxes[a_Index].m_Max = vector();
}

void md5_anim_parser::baseframe_block(){
	match(baseframe);
	match(open_brace);

	for(int i = 0; i < m_Animation.m_NumJoints; i++){
		baseframe_data(i);
	}

	match(close_brace);
}

void md5_anim_parser::baseframe_data(int a_Index){
	m_BaseFrameData[a_Index].m_Position = vector();
	m_BaseFrameData[a_Index].m_Rotation = quat();
}

void md5_anim_parser::frame_block(){
	for(int i = 0; i < m_Animation.m_NumFrames; i++){
		match(frame);

		int l_FrameIndex = m_Look.as_int();
		match(integer);

		match(open_brace);

		for(int i = 0; i < m_NumAnimatedComponents; i++){
			m_FrameData[i] = m_Look.as_float();
			match(real);
		}

		build_frame(l_FrameIndex);

		match(close_brace);
	}
}

void md5_anim_parser::build_frame(int a_Index){
	for(int i = 0; i < m_Animation.m_NumJoints; i++){
		base_frame_data &l_FrameData = m_BaseFrameData[i];

		math::vector3f    l_Position = l_FrameData.m_Position;
		math::quaternionf l_Rotation = l_FrameData.m_Rotation;

		int j = 0;

		j = set_data(i, pos_x, j, l_Position.m_X);
		j = set_data(i, pos_y, j, l_Position.m_Y);
		j = set_data(i, pos_z, j, l_Position.m_Z);

		j = set_data(i, rot_x, j, l_Rotation.m_X);
		j = set_data(i, rot_y, j, l_Rotation.m_Y);
		j = set_data(i, rot_z, j, l_Rotation.m_Z);

		l_Rotation = math::quaternionf::implicit_w(
			l_Rotation.m_X, l_Rotation.m_Y, l_Rotation.m_Z);

		md5_joint &l_Joint = m_Animation.m_SkeletalFrames[a_Index][i];

		l_Joint.m_Parent   = m_JointData[i].m_Parent;
		l_Joint.m_Name     = m_JointData[i].m_Name;

		// check if the parent has already been calculated
		if(l_Joint.m_Parent == md5_node::invalid_joint){
			error("Unknown parent for joint '" + l_Joint.m_Name + "'");
		}

		// if we have a parent, update position and orientation
		if(l_Joint.m_Parent >= 0){
			md5_joint &l_Parent 
				= m_Animation.m_SkeletalFrames[a_Index][l_Joint.m_Parent];


			// TODO: we might want to consider storing relative coordinates
			l_Position  = l_Parent.m_Rotation.rotate(l_Position);
			l_Position += l_Parent.m_Position;

			l_Rotation *= l_Parent.m_Rotation;
			l_Rotation  = l_Rotation.normalize();
		}

		l_Joint.m_Position = l_Position;
		l_Joint.m_Rotation = l_Rotation;
	}
}

int md5_anim_parser::set_data(int a_Index, int a_Mask, int a_Offset, float &a_Result){
	if(m_JointData[a_Index].m_Flags & a_Mask){
		a_Result = m_FrameData[m_JointData[a_Index].m_StartIndex + a_Offset];
		return a_Offset + 1;
	}

	return a_Offset;
}

bool md5_anim_parser::is_valid_eof(){
	try{
		m_Animation.validate();
	}catch(md5_node_exception &a_Exception){
		throw md5_parse_error(
			std::string("Invalid end of file: ") + a_Exception.what());
	}

	return m_Look.m_Tag == close_brace;
}
